//$Id

import java.util.ArrayList;

public class Gamestate {

	//VARIABLES//
	public enum Player{
		CIRCLE, CROSS, SQUARE; 
	}

	public static final int BOARD_SIZE = 19;	
	public Player[][] Board = new Player[BOARD_SIZE][BOARD_SIZE];
	public Player currentPlayer;
	public ArrayList<ArrayList<WinningCombination>> winningCombinations;

	public Gamestate(){
		winningCombinations = new ArrayList<ArrayList<WinningCombination>>();
		for (Player i : Player.values()){
			winningCombinations.add(new ArrayList<WinningCombination>());
		}
		currentPlayer = Player.CIRCLE;
	}
	
	public Gamestate(Gamestate old, Integer moveX, Integer moveY) throws Exception {
		this.Board = new Player[BOARD_SIZE][BOARD_SIZE];
		for (int i = 0; i < BOARD_SIZE; i++) {
			for (int j = 0; j < BOARD_SIZE; j++) {
				Board[i][j] = old.Board[i][j];
			}
		}
		this.currentPlayer = Player.values()[old.currentPlayer.ordinal()];
		
		this.winningCombinations = new ArrayList<ArrayList<WinningCombination>>();
		for (Player i : Player.values()) {
			ArrayList<WinningCombination> newList = new ArrayList<WinningCombination>();
			for (WinningCombination j : old.winningCombinations.get(i.ordinal())) {
				newList.add(j.clone());
			}
			winningCombinations.add(newList);
		}
		addMove(moveX, moveY);
	}
	
	public void addMove(Integer moveX, Integer moveY) throws Exception {
		// adds the move
		Board[moveX][moveY] = currentPlayer;
		for (Player player : Player.values()) {
			if (player == currentPlayer) {
				// add onto winning combinations
				Boolean[][] added = new Boolean[WinningCombination.WinningCombinationType.values().length][5];
				for (WinningCombination w : winningCombinations.get(player.ordinal())) {
					if (w.contains(moveX, moveY)) {
						w.mark(moveX, moveY);
						added[w.type.ordinal()][w.element(moveX, moveY)] = true;
					}
				}
				for (WinningCombination.WinningCombinationType i : WinningCombination.WinningCombinationType.values()) {
					for (int j = 0; j < 5; j++) {
						if (added[i.ordinal()][j] == null || added[i.ordinal()][j] != true) {
							// we add this inside the list
							switch (i) {
								case HORIZONTAL:
									if ((moveX - j >= 0) && (moveX + 4 - j < BOARD_SIZE)) {
										Boolean canAdd = true;
										for (int k = 0; k < 5; k++) {
											if (Board[moveX-j+k][moveY] != null && Board[moveX-j+k][moveY] != player) {
												canAdd = false;
											}
										}
										if (canAdd) {
											WinningCombination w = new WinningCombination((moveX-j), moveY, i);
											w.mark(moveX, moveY);
											winningCombinations.get(player.ordinal()).add(w);
										}
									} 
									break;
								case VERTICAL:
									if ((moveY - j >= 0) && (moveY + 4 - j < BOARD_SIZE)) {
										Boolean canAdd = true;
										for (int k = 0; k < 5; k++) {
											if (Board[moveX][moveY-j+k] != null && Board[moveX][moveY-j+k] != player) {
												canAdd = false;
											}
										}
										if (canAdd) {
											WinningCombination w = new WinningCombination(moveX, moveY-j, i);
											w.mark(moveX, moveY);
											winningCombinations.get(player.ordinal()).add(w);
										}
									}
									break;
								case DIAGONALUP:
									if ((moveX - j >= 0) && 
										(moveY - 4 + j >= 0) && 
										(moveX + 4 - j < BOARD_SIZE) &&
										(moveY + j < BOARD_SIZE)) {
										Boolean canAdd = true;
										for (int k = 0; k < 5; k++) {
											if (Board[moveX-j+k][moveY+j-k] != null && Board[moveX-j+k][moveY+j-k] != player) {
												canAdd = false;
											}
										}
										if (canAdd) {
											WinningCombination w = new WinningCombination(moveX-j, moveY+j, i);
											w.mark(moveX, moveY);
											winningCombinations.get(player.ordinal()).add(w);
										}
									}
									break;
								case DIAGONALDOWN:
									if ((moveX - j >= 0) && 
										(moveY - j >= 0) &&
										(moveX + 4 - j < BOARD_SIZE) &&
										(moveY + 4 - j < BOARD_SIZE)) {
										Boolean canAdd = true;
										for (int k = 0; k < 5; k++) {
											if (Board[moveX-j+k][moveY-j+k] != null && Board[moveX-j+k][moveY-j+k] != player) {
												canAdd = false;
											}
										}
										if (canAdd) {
											WinningCombination w = new WinningCombination(moveX-j, moveY-j, i);
											w.mark(moveX, moveY);
											winningCombinations.get(player.ordinal()).add(w);
										}
									}
									break;
							}		
						}
					}	
				}
				
				
			} else {
				// remove from winning combinations
				ArrayList<WinningCombination> listToRemove = new ArrayList<WinningCombination>();
				
				for (WinningCombination w : winningCombinations.get(player.ordinal())) {
					if (w.contains(moveX, moveY)) {
						listToRemove.add(w);
					}
				}
				for (WinningCombination w : listToRemove) {
					winningCombinations.get(player.ordinal()).remove(w);
				}
			}
		}
		this.nextPlayer();
	}
	
	public void parsePlayer(String sCurrentPlayer) {
		int nCurrentPlayer = Integer.valueOf(sCurrentPlayer) - 1;
		currentPlayer = Player.values()[nCurrentPlayer];		
	}

	public void parseGamestate(String sGamestate) throws Exception {
		if (!sGamestate.equals("0")){
			String[] splitString = sGamestate.split("\\)");
			for (int i = 0; i < splitString.length; i++){
				String sMove = splitString[i];
				sMove = sMove.substring(((i == 0) ? 1 : 2));
				String[] numbers = sMove.split("\\,");
				int nFirstIndex = (int)Integer.parseInt(numbers[0]) - 1;
				int nSecondIndex = (int)Integer.parseInt(numbers[1]) - 1;
				addMove(nFirstIndex, nSecondIndex);
			}	
		}	
	}
	
	public int getHeuristic(Player p) {
		//Algorithm: Infinity * (straight 5) + 10000 * (straight 4) + 500 * (straight 3) + 250 * (broken 3)
		
		//Only calculate if not calculated before
		//Here, we assume that the gamestate will not change once created, thus heuristic will not change
		//Should the gamestate changes, we need to remove this part
		int value = 0;
		for (WinningCombination w : winningCombinations.get(p.ordinal())) {
			if (w.straightFive()) {
				return Integer.MAX_VALUE;
			} else if (w.straightFour()) {
				value += 10000;
			} else if (w.straightThree()) {
				value += 500;
			} else if (w.hasThree()) {
				value += 250;
			}
		}
		return value;
	}

	public Player getNextPlayer() {
		switch(currentPlayer){
		case CIRCLE:
			return Player.CROSS;
		case CROSS:
			return Player.SQUARE;
		case SQUARE:
		default:
			return Player.CIRCLE;
		}
	}
	
	public String getCurrentPlayer(){
		switch(currentPlayer){
		case CIRCLE:
			return "CIRCLE";
		case CROSS:
			return "CROSS";
		case SQUARE:
			return "SQUARE";
		default:
			return "CIRCLE";
		}
	}

	
	//PRIVATE METHODS//
	private void nextPlayer(){
		switch(currentPlayer){
		case CIRCLE:
			currentPlayer = Player.CROSS;
			break;
		case CROSS:
			currentPlayer = Player.SQUARE;
			break;
		case SQUARE:
			currentPlayer = Player.CIRCLE;
			break;
		}
	}
}
